<?php

declare(strict_types=1);

namespace Frontend\Http;

class ApiClient
{
    public function __construct(
        private readonly string $baseUrl,
    ) {
    }

    public function get(string $path): array
    {
        return $this->request('GET', $path);
    }

    public function post(string $path, array $payload = []): array
    {
        return $this->request('POST', $path, ['json' => $payload]);
    }

    public function patch(string $path, array $payload = []): array
    {
        return $this->request('PATCH', $path, ['json' => $payload]);
    }

    private function request(string $method, string $path, array $options = [], bool $allowRetry = true): array
    {
        $url = $this->baseUrl . $path;
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        $headers = [
            'Accept: application/json',
        ];

        if (isset($options['json'])) {
            $payload = json_encode($options['json'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $headers[] = 'Content-Type: application/json';
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        }

        $cookieHeader = $this->buildCookieHeader();
        if ($cookieHeader) {
            $headers[] = 'Cookie: ' . $cookieHeader;
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);
        if ($response === false) {
            $error = curl_error($ch);
            curl_close($ch);
            return ['status' => 500, 'data' => ['message' => '网络请求失败', 'reason' => $error]];
        }

        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $rawHeaders = substr($response, 0, $headerSize);
        $body = substr($response, $headerSize);
        curl_close($ch);

        $cookies = $this->extractCookies($rawHeaders);
        $this->forwardCookies($cookies);

        if ($status === 401 && $allowRetry && $this->tryRefresh()) {
            return $this->request($method, $path, $options, false);
        }

        $data = json_decode($body, true);
        if ($data === null) {
            $data = ['raw' => $body];
        }

        return ['status' => $status, 'data' => $data];
    }

    private function buildCookieHeader(): string
    {
        $cookies = [];
        foreach (['access_token', 'refresh_token'] as $cookie) {
            if (!empty($_COOKIE[$cookie])) {
                $cookies[] = $cookie . '=' . $_COOKIE[$cookie];
            }
        }
        return implode('; ', $cookies);
    }

    private function extractCookies(string $rawHeaders): array
    {
        $cookies = [];
        foreach (explode("\r\n", $rawHeaders) as $line) {
            if (str_starts_with(strtolower($line), 'set-cookie:')) {
                $cookies[] = trim(substr($line, strlen('set-cookie:')));
            }
        }
        return $cookies;
    }

    private function forwardCookies(array $cookies): void
    {
        foreach ($cookies as $cookieLine) {
            $parts = array_map('trim', explode(';', $cookieLine));
            $nameValue = array_shift($parts);
            if (!$nameValue || !str_contains($nameValue, '=')) {
                continue;
            }
            [$name, $value] = explode('=', $nameValue, 2);
            $options = [
                'path' => '/',
                'httponly' => false,
                'secure' => false,
                'samesite' => 'Lax',
            ];
            foreach ($parts as $part) {
                if (str_contains($part, '=')) {
                    [$optName, $optValue] = explode('=', $part, 2);
                    $optName = strtolower($optName);
                    switch ($optName) {
                        case 'path':
                            $options['path'] = $optValue;
                            break;
                        case 'max-age':
                            $options['expires'] = time() + (int) $optValue;
                            break;
                        case 'expires':
                            $options['expires'] = strtotime($optValue);
                            break;
                        case 'samesite':
                            $options['samesite'] = $optValue;
                            break;
                    }
                } else {
                    $flag = strtolower($part);
                    if ($flag === 'httponly') {
                        $options['httponly'] = true;
                    }
                    if ($flag === 'secure') {
                        $options['secure'] = true;
                    }
                }
            }
            setcookie($name, $value, $options);
        }
    }

    private function tryRefresh(): bool
    {
        $refreshToken = $_COOKIE['refresh_token'] ?? null;
        if (!$refreshToken) {
            return false;
        }
        $response = $this->request(
            'POST',
            '/auth/refresh',
            ['json' => ['refresh_token' => $refreshToken]],
            false
        );
        return $response['status'] < 400;
    }
}
